package mt.dummy;

import java.util.ArrayList;
import java.util.LinkedList;

import mt.Order;
import mt.comm.ServerComm;
import mt.comm.ServerSideMessage;
import mt.server.MicroTraderServer;

public class MicroTraderServerDummy implements MicroTraderServer, Runnable {

	/** List of orders received */
	private LinkedList<Order> ordersList = new LinkedList<Order>();

	/** Unique order id */
	private int serverOrderID = 0;

	/** Server Communication object */
	private ServerComm serverComm;

	/** Thread boolean interrupted */
	private boolean interrupted = false;

	/** List of connected clients */
	private ArrayList<String> clientsList = new ArrayList<String>();

	@Override
	public void run() {
		serverComm.start();
		while (!interrupted) {

			if (serverComm.hasNextMessage()) {
				ServerSideMessage ssm = serverComm.getNextMessage();
				handleMessage(ssm);
			}
		}

	}

	/**
	 * The ServerSideMessage argument must have a valid Type. This method always
	 * returns immediately, whether or not argument is valid. There are 3
	 * different message types, whether it is a new order, a new connection, or
	 * if someone has disconnected. In case of a new order, placeNewOrder is
	 * called. If connected, the clientList is updated and all clients are
	 * updated by calling updateClient(). If someone disconnects, the
	 * disconnected client is removed and disconnectedClient() is called.
	 * 
	 * @param ServerSideMessage
	 *            containing a valid type
	 */
	public void handleMessage(ServerSideMessage ssm) {
		switch (ssm.getType()) {

		case NEW_ORDER:
			placeNewOrder(ssm.getOrder());
			break;
		case CONNECTED:
			clientsList.add(ssm.getSenderNickname());
			updateClient(ssm.getSenderNickname());
			break;
		case DISCONNECTED:
			clientsList.remove(ssm.getSenderNickname());
			disconnectClient(ssm.getSenderNickname());
			break;
		}
	}

	// COMPLETE, procura as primeiras hipoteses para poder faze compra/venda,
	// apagando ou inserindo dependendo das ordens j� existentes.
	/**
	 * Places new order on the list of orders. First checks if is a buy or a
	 * sell order, then depending on the orders in the list, it will delete
	 * orders in the list or insert order.
	 * 
	 * @param order
	 *            order received.
	 */
	public void placeNewOrder(Order order) {
		Order foundOrd;
		if (ordersList.isEmpty() || findMatchingOrder(order) == null) {
			addNewOrder(order);
			//System.out.println("1 : null : " + ordersList);
		}
		else
		while (!ordersList.isEmpty() && findMatchingOrder(order) != null) {
			foundOrd = findMatchingOrder(order);
			if (foundOrd.getNumberOfUnits() < order.getNumberOfUnits()) {

				order.setNumberOfUnits(order.getNumberOfUnits()
						- foundOrd.getNumberOfUnits());
				foundOrd.setNumberOfUnits(0);
				updateAllClients(foundOrd);
				ordersList.remove(foundOrd);
				addNewOrder(order);
				//System.out.println("2 : found < new : " + ordersList);
			} else if (foundOrd.getNumberOfUnits() == order.getNumberOfUnits()) {
				foundOrd.setNumberOfUnits(0);
				updateAllClients(foundOrd);
				ordersList.remove(foundOrd);
				//System.out.println("3 : found = new : " + ordersList);
				return;
			} else {
				foundOrd.setNumberOfUnits(foundOrd.getNumberOfUnits()
						- order.getNumberOfUnits());
				updateAllClients(foundOrd);
				//System.out.println("4 : found > new : " + ordersList);
				return;
			}
		}

//		updateAllClients();


	}

	/**
	 * Returns a matching order. The order {@link Order} must not be null or
	 * invalid, if it isn't neither a sellOrder or a buyOrder, Null will be
	 * returned. If the order is either a buying or selling order then the
	 * orders list {@link ordersList} is iterated and the matching order found
	 * is returned.
	 * 
	 * @param Order
	 *            to be compared.
	 * @return the first matching order found.
	 */
	public Order findMatchingOrder(Order order) {
		if (order.isSellOrder()) {
			for (Order o : ordersList)
				if (o.isBuyOrder() && o.getStock().equals(order.getStock())
						&& order.getPricePerUnit() <= o.getPricePerUnit())
					return o;

		} else if (order.isBuyOrder()) {
			for (Order o : ordersList)
				if (o.isSellOrder() && o.getStock().equals(order.getStock())
						&& order.getPricePerUnit() >= o.getPricePerUnit())
					return o;
		}
		return null;
	}

	/**
	 * Add's a new Order to the order list. When it add a order it generate an
	 * unique ID for the order.
	 * 
	 * @param order
	 *            new order received.
	 */
	public void addNewOrder(Order order) {
		order.setServerOrderID(serverOrderID);
		System.out.println("Send: "+order);
		ordersList.add(order);
		serverOrderID++;
		updateAllClients(order);
	}

	@Override
	public void start(ServerComm serverComm) {
		Thread thread = new Thread(this);
		this.serverComm = serverComm;
		thread.start();
	}

	/**
	 * Notify the client owner of the modified Order.
	 * 
	 * @param senderNickname
	 *            Client nick name.
	 */
	public void updateClient(String senderNickname) {
		for (Order order : ordersList)
			sendOrder(senderNickname, order);
	}

	/**
	 * Updates all Clients. Iterates both lists {@link clientsList, ordersList}
	 * and calls sendOrder for every order {@link Order}, for every client
	 * {@link Client}. If the order received is a selling or a buying order and
	 * if it has a valid price and stock, then the order found is returned.
	 * 
	 */
	public void updateAllClients() {
		for (String nickname : clientsList) {
			for (Order order : ordersList) {
				sendOrder(nickname, order);
			}
		}
	}

	/**
	 * Notify all the clients from the clients list that an order was changed.
	 * 
	 * @param order
	 *            order modified.
	 */
	public void updateAllClients(Order order) {
		for (String nickname : clientsList)
			sendOrder(nickname, order);
	}

	/**
	 * Send an order to the respective client, using client nickname.
	 * 
	 * @param receiversNickname
	 *            client nickname.
	 * @param order
	 *            order to send.
	 */
	private void sendOrder(String receiversNickname, Order order) {
		serverComm.sendOrder(receiversNickname, order);
	}

	/**
	 * Disconnects a client {@link Client} and updates everyone connected.
	 * Iterates the Orders List {@link ordersList} and adds every order
	 * {@link Order} from that client to a temporary list {@link remOrders}.
	 * Then it proceeds to remove the collection {@link ordersList} from the
	 * iterated list {@link ordersList}
	 * 
	 * @param String
	 *            containing a valid nickname
	 */
	public void disconnectClient(String nickname) {
		ArrayList<Order> remOrders = new ArrayList<Order>();

		for (Order order : ordersList) {
			if (order.getNickname().equals(nickname)) {
				remOrders.add(order);
			}
		}
		ordersList.removeAll(remOrders);

		serverComm.disconnectClient(nickname);
		updateAllClients();
	}
}
